<?php
/**
 * Backend methods for BucketTool CLI utility
 */

class CLIBucketTool {
	
/**********************************************
 * Member definitions
 **********************************************/ 


	const STORAGE_STANDARD = AmazonS3::STORAGE_STANDARD;
	const STORAGE_REDUCED = AmazonS3::STORAGE_REDUCED;
	
	const ACL_PUBLIC = AmazonS3::ACL_PUBLIC;
	const ACL_PRIVATE = AmazonS3::ACL_PRIVATE;
	const ACL_OPEN = AmazonS3::ACL_OPEN;
	const ACL_AUTH_READ = AmazonS3::ACL_AUTH_READ;
	const ACL_OWNER_READ = AmazonS3::ACL_OWNER_READ;
	const ACL_OWNER_FULL_CONTROL = AmazonS3::ACL_OWNER_READ;
	
	private $mode = null;
	private $flags = array(
		"storageMode" => null,
		"force" => null,
		"acl" => null,
		"recursive" => null,
		"help" => null,
		"master" => null,
		"this" => null,
		"clobber" => null,
		"progress" => null,
		"strict" => null,
		"verbose" => null
	);
	private $bucket = null;
	private $sdbDomainBase = null;
	private $sdbDomain = null;
	private $sdbDomainMasterList = null;
	private $sdbDomainVersionList = null;
	private $sdbDomainSyncProfiles = null;
	private $remoteFolder = null;
	private $remoteObject = null;
	private $remoteObjectVersionID = null;
	private $localObject = null;
	private $userSDBDomain = null;
	private $minutes = null;
	private $uuid = null;
	private $args = null;
	
	public $shortOpts = array("b:", "d:", "r", "s", "l:", "o:", "i:", "f", "m:", "h", "a:", "v", "V");
	public $longOpts = array(
		"bucket:",
		"folder:",
		"recursive",
		"standard",
		"public",
		"private",
		"open",
		"authread",
		"ownerread",
		"ownerfullcontrol",
		"localobject:",
		"remoteobject:",
		"versionid:",
		"force",
		"minutes:",
		"help",
		"sdbdomain:",
		"private",
		"profilename:",
		"this",
		"masterlocal",
		"masterremote",
		"uuid:",
		"clobber",
		"progress",
		"strict",
		"verbose"
	);
	
/**********************************************
 * Housekeeping methods
 **********************************************/ 


	public function __construct() {
		$this->flag("storageMode", CLIBucketTool::STORAGE_REDUCED);
		$this->flag("force", false);
		$this->flag("acl", CLIBucketTool::ACL_PRIVATE);
		$this->flag("recursive", false);
		$this->flag("this", false);
		$this->flag("progress", false);
		$this->flag("strict", false);
		$this->flag("verbose", false);
	}
	
	public function notify($str) {
		if ($this->flag("verbose") === true) {
			echo "[VERBOSE] " . date("Y-m-d H:i:s.u") . " mem (" . memory_get_usage() . ") ";
			$trace = debug_backtrace();
			$caller = $trace[1];
			if (isset($caller["class"])) {
				echo $caller["class"] . "->";
			}
			if (isset($caller["function"])) {
				echo $caller["function"] . "()";
			}	
			echo " ($str)\n";
		}
	}
	
	/**
   * Sets and returns specified variable 
   *
   * @param string $var The variable to set/get.
   * @param string $value The value in which to set the var.
   * @return mixed: value of $this->var.
   */
	private function getSet($var, $value) {
		if (!is_null($value)) {
			$this->$var = $value;
		}
		return $this->$var;
	}
	
	/**
   * Sets and returns specified execution mode; ensures WORM functionality 
   *
   * @param string $value The value in which to set the mode.
   * @return mixed: currently set mode
   */
	public function mode($value = null) {
		if (!is_null($value)) {
			if (!is_null($this->mode)) {
				throw new Exception (Errors::CLIBucketTool_MESG_MODE_ALREADY_SET, Errors::CLIBucketTool_CODE_MODE_ALREADY_SET);
			}
			$this->mode = $value;
		}
		return $this->mode;
	}
	
	/**
   * Sets and returns specified flag. Validates flag. 
   *
   * @param string $flag The flag to set
   * @param string $value The value in which to set the flag
   * @return mixed: currently value of $this->flag
   */
	public function flag($flag, $value = null) {
		if (!array_key_exists($flag, $this->flags)) {
			throw new Exception (Errors::CLIBucketTool_MESG_INVALID_FLAG, Errors::CLIBucketTool_CODE_INVALID_FLAG);
		}
		if (!is_null($value)) {
			$this->flags[$flag] = $value;
		}
		return $this->flags[$flag];
	}
	
	/**
   * Sets and returns bucket name 
   *
   * @param string $value Name of bucket
   * @return string: Bucket name
   */
	public function bucket($value = null) {
		if (!is_null($value)) {
			$this->bucket = $value;
			if (!is_null($this->sdbDomainBase) && is_null($this->sdbDomain)) {
				$this->sdbDomain = $this->sdbDomainBase . "-$value";
			}
		}
		return $this->bucket;
	}
	
	/**
   * Sets and returns remote object 
   *
   * @param string $value Name of remote object
   * @return string: Remote object name
   */
	public function remoteObject($value = null) {
		return $this->getSet("remoteObject", $value);
	}
	
	/**
   * Sets and returns remote object version ID 
   *
   * @param string $value remote object version ID
   * @return string: remote object version ID
   */
	public function remoteObjectVersionID($value = null) {
		return $this->getSet("remoteObjectVersionID", $value);
	}
	
	/**
   * Sets and returns local object name 
   *
   * @param string $value local object name
   * @return string: local object name
   */
	public function localObject($value = null) {
		return $this->getSet("localObject", $value);
	}
	
	/**
   * Sets and returns remote folder (S3 Prefix) name 
   *
   * @param string $value remote folder (S3 Prefix) name
   * @return string: remote folder (S3 Prefix) name
   */
	public function remoteFolder($value = null) {
		return $this->getSet("remoteFolder", $value);
	}
	
	/**
   * Sets and returns sdb domain name 
   *
   * @param string $value sdb domain name
   * @return string: sdb domain name
   */
	public function sdbDomain($value = null) {
		return $this->getSet("sdbDomain", $value);
	}
	
	/**
   * Sets and returns user set sdb domain name 
   *
   * @param string $value sdb domain name
   * @return string: sdb domain name
   */
	public function userSDBDomain($value = null) {
		return $this->getSet("userSDBDomain", $value);
	}
	
	/**
   * Sets and returns remote folder (S3 Prefix) name 
   *
   * @param string $value remote folder (S3 Prefix) name
   * @return string: remote folder (S3 Prefix) name
   */
	public function sdbDomainBase($value = null) {
		if (!is_null($value)) {
			$this->sdbDomainBase = $value;
			if (!is_null($this->bucket) && is_null($this->sdbDomain)) {
				$this->sdbDomain = "$value-" . $this->bucket;
				echo "sdbDomainBase setting sdbDomain: '" . $this->sdbDomain . "'\n";
			}
		}
		return $this->sdbDomainBase;
	}
	
	/**
   * Sets and returns minutes 
   *
   * @param string $value minutes in strtotime format
   * @return string: minutes
   */
	public function minutes($value = null) {
		return $this->getSet("minutes", $value);
	}
	
	/**
   * Sets and returns profile name 
   *
   * @param string $value new profile name
   * @return string: profile name
   */
	public function profilename($value = null) {
		return $this->getSet("profilename", $value);
	}
	
	/**
   * Sets and returns uuid 
   *
   * @param string $value new uuid
   * @return string: uuid
   */
	public function uuid($value = null) {
		return $this->getSet("uuid", $value);
	}
	
	/**
   * Sets and returns args
   *
   * @param string $value new args
   * @return string: args
   */
	public function args($value = null) {
		return $this->getSet("args", $value);
	}
	
	/**
   * Executes the Bucket Tool based upon the set mode 
   *
   * @return mixed: results of execution
   */
	public function execute() {
		if (is_null($this->mode())) {
			throw new Exception (Errors::CLIBucketTool_MESG_MODE_NOT_SET, Errors::CLIBucketTool_CODE_MODE_NOT_SET);
		}
		
		$method = $this->mode();
		if (method_exists($this, $method)) {
			//call_user_method($method, $this);
			call_user_func(array($this, $method));
		} else {
			throw new Exception (Errors::CLIBucketTool_MESG_MODE_NOT_DEFINED . " ($method)", Errors::CLIBucketTool_CODE_MODE_NOT_DEFINED);
		}
	}

/**********************************************
 * Methods related to DynamoDB
 **********************************************/ 
	public function bucketSyncToDDB() {
		if (is_null($this->bucket())) {
			Errors::helpDbaseSync();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		$db = new DynamoDB();
	}
		
	
/**********************************************
 * Methods related to SDB and SDB domain operations
 **********************************************/ 

	/**
   * Sync bucket contects to SDB 
   *
   * @return mixed: results
   */
	public function bucketSyncToSDB($remoteObject = null) {
		if ($this->flag("help")) {
			Errors::helpDbaseSync();
			return;
		}	
		
		if (is_null($this->bucket())) {
			Errors::helpDbaseSync();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		$sdb = new SDBUtils();
		$this->validateSDBDomain();
		if (!is_null($remoteObject)) {
			$this->notify("Fetching new object stats");
			$objectList = $s3->bucketGetObjects(true, $remoteObject);
		} else {
			$this->notify("Fetching bucket objects (" . $this->bucket() . ")");
			$objectList = $s3->bucketGetObjects(false);
		}
		$totalObjectsToSync = count($objectList);
		$this->notify("Syncing $totalObjectsToSync objects");
		$count = 0;
		foreach ($objectList as $objectName => $object) {
			$count++;
			$pct = ($count / $totalObjectsToSync) * 100;
			$this->notify(sprintf("%d / %d (%3.2f%%) Syncing version(s) (%d) of object %s", $count, $totalObjectsToSync, $pct, count($object), $objectName));
			$latest = null;
			if (isset($object["type"]) && ($object["type"] == S3Utils::FILE_TYPE_DIR)) {
				continue;
			} elseif (is_array($object)) {
				foreach ($object as $versionID => $version) {
					$this->notify("\tversion $versionID");
					if (isset($version["isLatest"]) && $version["isLatest"]) {
						$latest = $versionID;
					}
					$keypairs = array(
						"name" => $objectName,
						"type" => $version["type"],
						"acl" => $version["acl"],
						"LastModified" => $version["LastModified"],
						"md5" => $version["md5"],
						"size" => $version["size"],
						"storage" => $version["storage"],
						"owner" => json_encode($version["owner"])
					);
					$sdb->itemPut($this->sdbDomain . "-versions", $versionID, $keypairs, true);
				}
				$sdb->itemPut($this->sdbDomain . "-masterList", $objectName, array("latestVersionID" => $latest, "versionCount" => count($objectName)), true);
			}
		}
	}
	
	/**
   * Resets the SDB domain for the specified bucket 
   *
   * @return mixed: results
   */
	public function resetSDB() {
		if ($this->flag("help")) {
			Errors::helpDbaseReset();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpDbaseReset();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->sdbDomainBase())) {
			Errors::helpDbaseReset();
			throw new Exception (Errors::CLIBucketTool_MESG_SDB_BASE_DOMAIN_NOT_SET, Errors::CLIBucketTool_CODE_SDB_BASE_DOMAIN_NOT_SET);
		} 
		
		$this->sdbDomain = $this->sdbDomainBase() . "-" .$this->bucket();
		$sdb = new SDBUtils();
		
		$master   = $this->sdbDomain . "-masterList";
		$versions = $this->sdbDomain . "-versions";
		$syncProfiles = $this->sdbDomain . "-syncProfiles";
		
		echo "Deleteing domain ($master)\n"; 
		$sdb->domainDelete($master);
		
		echo "Deleteing domain ($versions)\n"; 
		$sdb->domainDelete($versions);
		
		echo "Deleteing domain ($syncProfiles)\n"; 
		$sdb->domainDelete($syncProfiles);
		
		$this->bucketSyncToSDB();
	}


	/**
   * Validates SDB domian; sets up domain, if needed 
   *
   * @return mixed: results
   */
	private function validateSDBDomain() {
		if (is_null($this->sdbDomain())) {
			throw new Exception (Errors::CLIBucketTool_MESG_SDB_DOMAIN_NOT_SET, Errors::CLIBucketTool_CODE_SDB_DOMAIN_NOT_SET);
		}
		
		$sdb = new SDBUtils();
		$this->sdbDomainMasterList = $this->sdbDomain() . "-masterList";
		$this->sdbDomainVersionList = $this->sdbDomain() . "-versions";
		$this->sdbDomainSyncProfiles = $this->sdbDomain() . "-syncProfiles";
		 
		if (!$sdb->domainExist($this->sdbDomainMasterList)) {
			echo "SDB domain (" . $this->sdbDomainMasterList . ") does not exist... creating.\n";
			$sdb->domainCreate($this->sdbDomainMasterList);
		}
		
		if (!$sdb->domainExist($this->sdbDomainVersionList)) {
			echo "SDB domain (" . $this->sdbDomainVersionList . ") does not exist... creating.\n";
			$sdb->domainCreate($this->sdbDomainVersionList);
		}
		
		if (!$sdb->domainExist($this->sdbDomainSyncProfiles)) {
			echo "SDB domain (" . $this->sdbDomainSyncProfiles . ") does not exist... creating.\n";
			$sdb->domainCreate($this->sdbDomainSyncProfiles);
		}
		return true;
	}
	
	private function removeSDBDomains() {
		if (is_null($this->sdbDomain())) {
			throw new Exception (Errors::CLIBucketTool_MESG_SDB_DOMAIN_NOT_SET, Errors::CLIBucketTool_CODE_SDB_DOMAIN_NOT_SET);
		}
		
		$sdb = new SDBUtils();
		$this->sdbDomainMasterList = $this->sdbDomain() . "-masterList";
		$this->sdbDomainVersionList = $this->sdbDomain() . "-versions";
		$this->sdbDomainSyncProfiles = $this->sdbDomain() . "-syncProfiles";
		
		echo "Deleteing SDB domain (" . $this->sdbDomainMasterList . ")\n";
		$sdb->domainDelete($this->sdbDomainMasterList);
		
		echo "Deleteing SDB domain (" . $this->sdbDomainVersionList . ")\n";
		$sdb->domainDelete($this->sdbDomainVersionList);
		
		echo "Deleteing SDB domain (" . $this->sdbDomainSyncProfiles . ")\n";
		$sdb->domainDelete($this->sdbDomainSyncProfiles);
		return true;
	}
	
	public function SDBListDomains() {
		if ($this->flag("help")) {
			Errors::helpDbaseList();
			return;
		}
		$sdb = new SDBUtils();
		$domains = $sdb->domainList();
		if (!is_null($this->sdbDomain())) {
			$disp = "SDB Domains related to S3 bucket: ". $this->bucket();
			$dispCount = strlen($disp) + 2;
			printf("%s\n%'={$dispCount}s\n", $disp, "");
			foreach ($domains as $domain) {
				if (false !== strpos($domain, $this->bucket())) {
					echo "$domain\n";
				}
			}
		} else {
			echo "SDB Domains:\n";
			echo "===============\n";
			foreach ($domains as $domain) {
				echo "$domain\n";
			}
		}
	}
	
	public function SDBListItems() {
		if ($this->flag("help")) {
			Errors::helpDbaseItems();
			return;
		}
		if (is_null($this->userSDBDomain())) {
			Errors::helpDbaseItems();
			throw new Exception (Errors::CLIBucketTool_MESG_USER_SDB_DOMAIN_NOT_SET, Errors::CLIBucketTool_CODE_USER_SDB_DOMAIN_NOT_SET);
		}
		$sdb = new SDBUtils();
		$items = $sdb->domainGetAll($this->userSDBDomain());
		if (is_null($items)) {
			echo "Domain contains no items\n";
			return;
		}
		print_r($items);
		return true;
	}


 
/**********************************************
 * Methods related to bucket operations
 **********************************************/ 

	/**
   * Creates a bucket 
   *
   * @return mixed: results
   */
	public function bucketCreate() {
		if ($this->flag("help")) {
			Errors::helpBucketCreate();
			return;
		}		
		
		if (is_null($this->bucket())) {
			Errors::helpBucketCreate();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		if ($s3->bucketCreate($this->flag("acl"))) {
			echo "Bucket (" . $this->bucket() . ") created\n";
			$this->bucketVersioningEnable();
			$this->validateSDBDomain();
		}
	}

	/**
   * Deletes a bucket 
   *
   * @return mixed: results
   */
	public function bucketDelete() {
		if ($this->flag("help")) {
			Errors::helpBucketDelete();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpBucketDelete();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (!$this->flag("force")) {
			echo "Force flag not set; bucket delete will probably fail\n";
		}
		
		$s3 = new S3Utils($this->bucket());
		if ($s3->bucketDelete($this->flag("force"))) {
			echo "Bucket (" . $this->bucket() . ") deleted\n";
			$this->removeSDBDomains();
		}
	}

	/**
   * Generates a list of S3 buckets 
   *
   * @return mixed: results
   */
	public function bucketsList() {
		if ($this->flag("help")) {
			echo "Shows a list of available S3 buckets\nNo additional options are available\n";
			return;
		}
		
		$s3 = new S3Utils();
		$buckets = $s3->bucketList();
		if (is_null($buckets)) {
			$buckets[] = array("name" => "(no buckets returned)", "createDate" => null);
		}
		$maxNameLength = 1;
		foreach ($buckets as $bucket) {
			if (strlen($bucket["name"]) > $maxNameLength) {
				$maxNameLength = strlen($bucket["name"]) + 2;
			}
		}
		
		$format = "%-{$maxNameLength}s\t%-25s\n";
		$formatPadding = "%'={$maxNameLength}s\t%'=25s\n";
		printf($format, "Bucket Name", "Create Date");
		printf($formatPadding, "", "");
		foreach ($buckets as $bucket) {
			printf($format, $bucket["name"], is_null($bucket["createDate"]) ? "--" : date("c", $bucket["createDate"]));
		}
	}

	/**
   * Generates a list of objects as requested 
   *
   * @return mixed: results
   */
	public function bucketListObjects() {
		$this->notify("bucket list ordered; setting it up");
		if ($this->flag("help")) {
			Errors::helpBucketListObjects();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpBucketListObjects();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->sdbDomain())) {
			Errors::helpBucketListObjects();
			throw new Exception (Errors::CLIBucketTool_MESG_SDB_DOMAIN_NOT_SET, Errors::CLIBucketTool_CODE_SDB_DOMAIN_NOT_SET);
		}
		
		$this->notify("Everything is in its right place. Calling S3DB");
		$s3sdb = new S3SDB($this->sdbDomain);
		$s3sdb->verbose = $this->flag("verbose");
		
		foreach ($s3sdb->getBucketObjects($this->remoteFolder) as $object => $data) {
			if ($data["deleted"] === true) {
				echo "**DELETED: ";
			}
			echo "$object {$data["size"]} {$data["mtime"]} versions ({$data["versionCount"]}) latest version ID ({$data["latestVersionID"]})\n";
		}
	}
	
	/**
   * Enables bucket versioning 
   *
   * @return mixed: results
   */
	public function bucketVersioningEnable() {
		if ($this->flag("help")) {
			Errors::helpBucketVersioningEnable();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpBucketVersioningEnable();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		if ($s3->bucketEnableVersioning()) {
			echo "Versioning enabled\n";
			return true;
		} else {
			echo "Unable to enable versioning\n";
			return false;
		}
	}
	
	/**
   * Enables bucket versioning 
   *
   * @return mixed: results
   */
	public function bucketVersioningDisable() {
		if ($this->flag("help")) {
			Errors::helpBucketVersioningEnable();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpBucketVersioningEnable();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		if ($s3->bucketDisableVersioning()) {
			echo "Versioning disabled\n";
			return true;
		} else {
			echo "Unable to disable versioning\n";
			return false;
		}
	}

	/**
   * Gets bucket versioning status 
   *
   * @return mixed: results
   */
	public function bucketVersioningStatus() {
		if ($this->flag("help")) {
			Errors::helpBucketVersioningStatus();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpBucketVersioningStatus();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		if ($s3->bucketIsVersioningEnabled()) {
			echo "Versioning is enabled\n";
			return true;
		} else {
			echo "Versioning is not enabled\n";
			return false;
		}
	}

	/**
   * Generates a list of raw bucket objects
   *
   * @return mixed: results
   */	
	public function bucketListRawObjects() {
		if ($this->flag("help")) {
			Errors::helpBucketListRawObjects();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpBucketVersioningStatus();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		$objs = $s3->bucketGetObjects(false);
		var_dump($objs);
	}
	
	/**
   * Deletes objects; one by one using actual S3 results... no dbase
   *
   * @return mixed: results
   */
	public function bucketSlowObjectNuke() {
		if ($this->flag("help")) {
			Errors::helpBucketSlowObjectNuke();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpBucketVersioningStatus();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		$s3->slowObjectAllNuke();
	}

/**********************************************
 * Methods related to object operations
 **********************************************/ 

/**
   * Compiles data related to an object version ID 
   *
   * @param string $objectVersionID version ID
   * @return mixed: results
   */
	public function objectStat() {
		if ($this->flag("help")) {
			Errors::helpObjectStat();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpObjectStat();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->remoteObject())) {
			Errors::helpObjectStat();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_NOT_SET);
		}
		
		$sdb = new SDBUtils();
		$s3 = new S3Utils();
		$this->sdbDomainMasterList = $this->sdbDomain() . "-masterList";
		$this->sdbDomainVersionList = $this->sdbDomain() . "-versions";
		$this->notify("Fetching item (" . $this->remoteObject() . ") from SDB domain (" . $this->sdbDomainMasterList . ")");
		$masterItem = $sdb->itemGet($this->sdbDomainMasterList, $this->remoteObject());
		if (!isset($masterItem["latestVersionID"])) {
			throw new Exception (Errors::CLIBucketTool_MESG_OBJECT_STAT_MASTER_FETCH_FAIL, Errors::CLIBucketTool_CODE_OBJECT_STAT_MASTER_FETCH_FAIL);
		}
		
		$latestVersionID = trim($masterItem["latestVersionID"]) == "" ? null : $masterItem["latestVersionID"];
		$this->notify("Got item with latestVersionID (" . gettype($latestVersionID) . " : $latestVersionID)");
		$this->notify("Fetching versions");
		$items = $sdb->domainSelect("SELECT * FROM `" . $this->sdbDomainVersionList . "` WHERE name = '" . $this->remoteObject() . "'");
		$count = count($items);
		$this->notify("Fetched $count version(s)");
		echo "===> " . $this->remoteObject() . " <===";
		if (is_null($latestVersionID) && $count > 0) { 
			echo " *** DELETED ***";
		}
		echo "\n";
		foreach ($items as $version) {
			echo "Version ID: {$version["uuid"]}";
			if ($latestVersionID == $version["uuid"]) {
				echo "     *** LATEST ***";
			}
			echo "\n";
			echo "\tLast Modified:  " . date("c", $version["LastModified"]) . "\n";
			echo "\tSize:           " . Utils::getHumanSize($version["size"]) . "\n";
			echo "\tMD5:            {$version["md5"]}\n";
			echo "\tStorge:         {$version["storage"]}\n";
			echo "\tACL:            " . $s3->aclList[$version["acl"]] . "\n";
			echo "\tType:           " . ((($version["type"] || S3Utils::FILE_TYPE_FILE) == S3Utils::FILE_TYPE_FILE) ? "File" : "Dir") . "\n";
			echo "\n";
		}
		
	}

	/**
   * Deletes versions of an object where no latest
   * exists. ie: file was deleted, but versions were not. 
   *
   * @return mixed: results
   */
   
   public function objectPurgeStales() {
   	if ($this->flag("help")) {
			Errors::helpObjectPurgeStales();
			return;
		}
		if (is_null($this->bucket())) {
			Errors::helpObjectPurgeStales();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$s3sdb = new S3SDB($this->sdbDomain);
		$s3sdb->verbose = $this->flag("verbose");
		
		$objects = $s3sdb->getBucketObjects();
		foreach ($objects as $object => $data) {
			if ($data["deleted"] === true) {
				$this->notify("$object marked as deleted");
				$this->remoteObjectVersionID("*");
				$this->remoteObject($object);
				$this->objectDelete();
			}
		}
   }

	/**
   * Deletes an object(s) in a bucket 
   *
   * @return mixed: results
   */
	public function objectDelete() {
		if ($this->flag("help")) {
			Errors::helpObjectDelete();
			return;
		}
		if (is_null($this->bucket())) {
			Errors::helpObjectDelete();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->remoteObject())) {
			Errors::helpObjectDelete();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_NOT_SET);
		}
		
		if (is_null($this->remoteObjectVersionID())) {
			Errors::helpObjectDelete();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_VERSION_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_VERSION_NOT_SET);
		}
		
		$this->notify("Deleting object (" . $this->remoteObject() . ") id (" . $this->remoteObjectVersionID() . ") from bucket (" . $this->bucket() . ")");
		$s3 = new S3Utils($this->bucket());
		return $s3->objectDelete($this->sdbDomain(), $this->remoteObject(), $this->remoteObjectVersionID());
	}
	
	/**
   * Uploads an object(s) as requested to S3 bucket 
   *
   * @return mixed: results
   */
	public function objectUpload() {
		if ($this->flag("help")) {
			Errors::helpObjectUpload();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpObjectUpload();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->localObject())) {
			Errors::helpObjectUpload();
			throw new Exception (Errors::CLIBucketTool_MESG_LOCAL_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_LOCAL_OBJECT_NOT_SET);
		}
		
		if (is_dir($this->localObject()) && ($this->flag("recursive") !== true)) {
			Errors::helpObjectUpload();
			throw new Exception (Errors::CLIBucketTool_MESG_LOCAL_OBJECT_DIR_BUT_NO_RECURSE, Errors::CLIBucketTool_CODE_LOCAL_OBJECT_DIR_BUT_NO_RECURSE);
		}
		
		$this->validateSDBDomain();
		
		$globs = glob($this->localObject());
		
		if (!is_array($globs)) {
			$globs = array($this->localObject());
		}

		$s3 = new S3Utils($this->bucket(), $this->remoteFolder());
		//$s3->progress = $this->flag("progress");
		foreach ($globs as $file) {
			echo "Uploading $file to " .	$this->bucket() . ":/" . $this->remoteFolder() . 
				" with" . ($this->flag("recursive") ? " " : "out ") . "recursion as" .
				($this->flag("acl") == self::ACL_PUBLIC ? " public" : " private") .
				", storage mode " . ($this->flag("storageMode") == self::STORAGE_STANDARD ? "standard" : "reduced redundancy") . ".\n";	
			$s3->objectUpload($this->sdbDomain(), $file, null, $this->flag("recursive"), $this->flag("acl"), $this->flag("storageMode"));
		}
		return true;
	}
	
	/**
   * Fetches a public URL for an object 
   *
   * @return mixed: results
   */
	public function objectURL() {
		if ($this->flag("help")) {
			Errors::helpObjectURL();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpObjectURL();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->remoteObject())) {
			Errors::helpObjectURL();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		try {
			$url = $s3->objectGetUrl($this->remoteObject(), $this->remoteObjectVersionID(), $this->minutes());
			echo "Url for " . $this->bucket() . ":/" . $this->remoteObject() . "\n";
			var_dump($url); //\n";
		} catch (Exception $e) {
			echo "Exception " . $e->getCode . ": " . $e->getMessage() . "\n";
		}
			
	}
	
	/**
   * Sets object ACL
   *
   * @return mixed: results
   */
	public function objectSetACL() {
		if ($this->flag("help")) {
			Errors::helpObjectSetACL();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpObjectSetACL();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->remoteObject())) {
			Errors::helpObjectSetACL();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_NOT_SET);
		}
		echo "Not yet implemented...\n";
	}
	
	/**
   * Sets object ACL
   *
   * @return mixed: results
   */
	public function objectGetACL() {
		if ($this->flag("help")) {
			Errors::helpObjectGetACL();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpObjectGetACL();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->remoteObject())) {
			Errors::helpObjectGetACL();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_NOT_SET);
		}

		$s3 = new S3Utils($this->bucket());
		$s3->objectGetACL($this->remoteObject(), $this->remoteObjectVersionID());
	}
	
	/**
   * Downloads an object
   *
   * @return mixed: results
   */
	public function objectDownload() {
		if ($this->flag("help")) {
			Errors::helpObjectDownload();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpObjectDownload();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->remoteObject())) {
			Errors::helpObjectDownload();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_NOT_SET);
		}
		
		if (is_null($this->localObject())) {
			Errors::helpObjectDownload();
			throw new Exception (Errors::CLIBucketTool_MESG_LOCAL_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_LOCAL_OBJECT_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		$s3->objectDownload($this->remoteObject(), $this->localObject(), $this->remoteObjectVersionID());		
	}
	
	/**
   * Get/Sets an object's storage mode
   *
   * @return mixed: results
   */
	public function objectStorage() {
		if ($this->flag("help")) {
			Errors::helpObjectStorage();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpObjectStorage();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->remoteObject())) {
			Errors::helpObjectStorage();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_NOT_SET);
		}
		
		$s3 = new S3Utils($this->bucket());
		$s3->objectSetStorage($this->remoteObject(), $this->flag("storageMode"));
		$this->bucketSyncToSDB($this->remoteObject());
	}
	
/**********************************************
 * Methods related to sync operations
 **********************************************/ 
	
	public function syncList() {
		if ($this->flag("help")) {
			Errors::helpSyncList();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpSyncList();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		$this->validateSDBDomain();
		
		$domain = $this->sdbDomainSyncProfiles;
		
		$sync = new syncProfile();
		
		if ($this->flag("this")) {
			$sync->host(gethostname());
		}
		
		$profiles = $sync->getSyncList();
		
		if (is_null($profiles)) {
			echo "No profiles created\n";
			return;
		}
		
		foreach ($profiles as $profile) {
			echo "{$profile["profileName"]}\n";
			echo "\tUUID: {$profile["uuid"]}\n";
			echo "\tBucket: {$profile["bucket"]}\n";
			echo "\tSystem: {$profile["hostName"]}\n";
			$remote = trim($profile["remoteObject"]);
			if ($remote == "") {
				$remote = "--root of bucket--";
			}
			if ($profile["master"] == CLIBucketTool::MASTER_LOCAL) {
				echo "\tLocal ({$profile["localObject"]}) ==> Remote ($remote)\n";
			} else {
				echo "\tRemote ($remote) ==> Local ({$profile["localObject"]})\n";
			}
		}
	}
	
	public function syncStart() {
		if ($this->flag("help")) {
			Errors::helpSyncStart();
			return;
		}
		//Utils::getHostnameHash();
	}
	
	public function syncNew() {
		if ($this->flag("help")) {
			Errors::helpSyncNew();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpSyncNew();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->profileName())) {
			Errors::helpSyncNew();
			throw new Exception (Errors::CLIBucketTool_MESG_PROFILE_NAME_NOT_SET, Errors::CLIBucketTool_CODE_PROFILE_NAME_NOT_SET);
		}
		
		if (is_null($this->localObject())) {
			Errors::helpObjectDownload();
			throw new Exception (Errors::CLIBucketTool_MESG_LOCAL_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_LOCAL_OBJECT_NOT_SET);
		}
		
		if (is_null($this->flag("master"))) {
			Errors::helpObjectDownload();
			throw new Exception (Errors::CLIBucketTool_MESG_FLAG_MASTER_LOCAL_NOT_SET, Errors::CLIBucketTool_CODE_FLAG_MASTER_LOCAL_NOT_SET);
		}
		
		$this->validateSDBDomain();
		$sync = new syncProfile();
		
		$sync->host(gethostname());
		$sync->name($this->profileName());
		$sync->remote($this->remoteObject());
		$sync->local($this->localObject());
		$sync->bucket($this->bucket());
		$sync->master($this->flag("master"));
		$sync->domain($this->sdbDomainSyncProfiles);
		
		$newProfile = $sync->defineNewProfile();
		
		
		if ($newProfile === true) {
			echo "Created new profile: " . $sync->name() ."\n";
			return true;
		}
	}
	
	public function syncDelete() {
		if ($this->flag("help")) {
			Errors::helpSyncDelete();
			return;
		}

		if (is_null($this->bucket())) {
			Errors::helpSyncDelete();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->uuid())) {
			Errors::helpSyncDelete();
			throw new Exception (Errors::CLIBucketTool_MESG_UUID_NOT_SET, Errors::CLIBucketTool_CODE_UUID_NOT_SET);
		}
		
		$sdb = new SDBUtils();
		$this->validateSDBDomain();
		$sdb->itemDelete($this->sdbDomainSyncProfiles, $this->uuid());
		echo "Profile deleted\n";
	}
	
	public function syncOneShot() {
		if ($this->flag("help")) {
			Errors::helpSyncOneShot();
			return;
		}
		
		if (is_null($this->bucket())) {
			Errors::helpSyncOneShot();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->localObject())) {
			Errors::helpSyncOneShot();
			throw new Exception (Errors::CLIBucketTool_MESG_LOCAL_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_LOCAL_OBJECT_NOT_SET);
		}
		
		if (is_null($this->flag("master"))) {
			Errors::helpSyncOneShot();
			throw new Exception (Errors::CLIBucketTool_MESG_FLAG_MASTER_LOCAL_NOT_SET, Errors::CLIBucketTool_CODE_FLAG_MASTER_LOCAL_NOT_SET);
		}
		
		if (is_null($this->flag("clobber"))) {
			$this->flag("clobber", false);
		}
		
		if (is_null($this->sdbDomain())) {
			Errors::helpBucketListObjects();
			throw new Exception (Errors::CLIBucketTool_MESG_SDB_DOMAIN_NOT_SET, Errors::CLIBucketTool_CODE_SDB_DOMAIN_NOT_SET);
		}
		
		$this->validateSDBDomain();
		$sync = new SyncProfile();
		
		
		$sync->host(gethostname());
		$sync->name("oneshot-" . uniqid());
		$sync->remote($this->remoteObject());
		$sync->local($this->localObject());
		$sync->bucket($this->bucket());
		$sync->master($this->flag("master"));
		$sync->domain($this->sdbDomain());
		$sync->oneshot(true);
		$sync->clobber($this->flag("clobber"));
		$sync->progress($this->flag("progress"));
		$sync->strict($this->flag("strict"));
		$sync->verbose($this->flag("verbose"));
		
		$oneShot = $sync->runSync();
	}
	
	public function awslibS3Version() {
		echo "AWS S3 Library Version: " . AmazonEC2::VERSION . "\n";
		return true;
	}
	
	public function awslibS3APIName() {
		echo "AWS S3 Library API Name: " . AmazonEC2::NAME . "\n";
		return true;
	}
	
	public function awslibS3APIUserAgent() {
		echo "AWS S3 Library API User Agent: " . AmazonEC2::USERAGENT . "\n";
		return true;
	}
	
	public function awslibGPGEncrypt() {
		if (is_null($this->bucket())) {
			Errors::helpAWSLibGPGDecrypt();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->localObject())) {
			Errors::helpAWSLibGPGEncrypt();
			throw new Exception (Errors::CLIBucketTool_MESG_LOCAL_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_LOCAL_OBJECT_NOT_SET);
		}
		
		if (!file_exists($this->localObject())) {
			Errors::helpAWSLibGPGEncrypt();
			throw new Exception (Errors::CLIBucketTool_MESG_LOCAL_OBJECT_NOT_EXIST, Errors::CLIBucketTool_CODE_LOCAL_OBJECT_NOT_EXIST);
		}
		
		if (is_null($this->remoteObject())) {
			Errors::helpAWSLibGPGEncrypt();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_NOT_SET);
		}
		
		$gpg = new GPG(array("identity" => "rsigler@gmail.com", "passphrase" => "pc%Z^eehDqkn81fjubkbL0GW"));
		
		$outStream = "s3://" . $this->bucket() . "/" . $this->remoteObject();
		$oStream = fopen($outStream, "w");
		fseek($oStream, 0);
		$iStream = fopen($this->localObject(), "r");
		$iSize = filesize($this->localObject());
		
		$this->validateSDBDomain();
		$gpg->encrypt($iStream, $oStream, $iSize);
		
		fflush($oStream);
		fclose($oStream);
		fclose($iStream);
	}
	
	public function awslibGPGDecrypt() {
		if (is_null($this->bucket())) {
			Errors::helpAWSLibGPGDecrypt();
			throw new Exception (Errors::CLIBucketTool_MESG_BUCKET_NOT_SET, Errors::CLIBucketTool_CODE_BUCKET_NOT_SET);
		}
		
		if (is_null($this->localObject())) {
			Errors::helpAWSLibGPGDecrypt();
			throw new Exception (Errors::CLIBucketTool_MESG_LOCAL_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_LOCAL_OBJECT_NOT_SET);
		}
		
		if (is_null($this->remoteObject())) {
			Errors::helpAWSLibGPGDecrypt();
			throw new Exception (Errors::CLIBucketTool_MESG_REMOTE_OBJECT_NOT_SET, Errors::CLIBucketTool_CODE_REMOTE_OBJECT_NOT_SET);
		}

		$gpg = new GPG(array("identity" => "rsigler@gmail.com", "passphrase" => "pc%Z^eehDqkn81fjubkbL0GW"));
		
		$S3stream = "s3://" . $this->bucket() . "/" . $this->remoteObject();
		$iStream = fopen($S3stream, "r");
		$oStream = fopen($this->localObject(), "w");
		
		$this->validateSDBDomain();
		$gpg->encrypt($iStream, $oStream);
		
		fclose($oStream);
		fclose($iStream);
	}
	
	public function SDBSelect() {
		if (is_null($this->args)) {
			Errors::helpDbaseSelect();
			throw new Exception (Errors::CLIBucketTool_MESG_SDB_QUERY_MISSING, Errors::CLIBucketTool_CODE_SDB_QUERY_MISSING);
		}
		
		if (!isset($this->args[0])) {
			Errors::helpDbaseSelect();
			throw new Exception (Errors::CLIBucketTool_MESG_SDB_QUERY_MISSING, Errors::CLIBucketTool_CODE_SDB_QUERY_MISSING);
		}
					
		$query = $this->args[0];
		$sdb = new SDBUtils();
		echo "Executing query: $query\n";
		$res = $sdb->domainSelect($query);
		echo "Returned " . count($res) . " result(s)\n"; 
		print_r($res);
		return;
	}
		
}
?>
